import 'dart:ui';

/// [SelectionManager]：用于管理文本或文档中的拖拽选择状态与计算逻辑。
///
/// 主要功能：
/// - 维护是否处于拖拽选择状态（`isSelecting`）
/// - 保存拖拽起点（文档绝对位置，`anchor`）
/// - 保存最近一次指针位置（经过裁剪后的本地坐标，`lastLocal`）
/// - 提供方法将指针的本地坐标转换为文档中的绝对位置
///
/// 使用流程示例：
/// 1. 在 [GestureDetector.onPanStart] 调用 [start]，传入当前指针位置和文档锚点位置；
/// 2. 在 [GestureDetector.onPanUpdate] 或滚动后调用 [update]，传入指针位置、视口高度和坐标映射回调，获得文档绝对位置；
/// 3. 在 [GestureDetector.onPanEnd] 或 [GestureDetector.onPanCancel] 调用 [end]，结束选择；
/// 4. 外部可通过 [lastLocal]、[anchor]、[isSelecting] 获取当前状态，用于自动滚动、光标绘制或逻辑判断。
class SelectionManager {
  bool _isSelecting = false;
  int? _anchor;
  Offset? _lastLocal; // 始终保存“裁剪后的”本地坐标，避免越界值被误用

  /// 是否正在进行拖拽选择。
  ///
  /// - `true`：当前正在选择（用户指针按下并拖拽中）
  /// - `false`：当前未处于选择状态
  bool get isSelecting => _isSelecting;

  /// 拖拽选择的锚点位置（文档绝对位置）。
  ///
  /// - 在调用 [start] 时初始化；
  /// - 用于确定选区的起点；
  /// - 在 [end] 时会被清空。
  int? get anchor => _anchor;

  /// 最近一次指针的本地坐标（已裁剪到视口范围内）。
  ///
  /// - 保证坐标始终在 `[0, viewportHeight)` 的范围内；
  /// - 用于外部逻辑（如自动滚动）计算指针相对位置；
  /// - 在 [end] 时会被清空。
  Offset? get lastLocal => _lastLocal;

  /// 开始一次拖拽选择。
  ///
  /// - [local] 当前指针的本地坐标（尚未裁剪）
  /// - [anchorDocPos] 该点在文档中的绝对位置（作为选区锚点）
  ///
  /// 调用后：
  /// - [isSelecting] 置为 `true`
  /// - [anchor] 记录文档绝对位置
  /// - [lastLocal] 先记录传入的 `local`（在后续 [update] 时会被裁剪覆盖）
  void start(Offset local, int anchorDocPos) {
    _isSelecting = true;
    _anchor = anchorDocPos;
    _lastLocal = local;
  }

  /// 更新拖拽过程中的指针位置，并返回对应的“文档绝对位置”。
  ///
  /// - [local] 当前指针的本地坐标（可能越界）
  /// - [viewportHeight] 视口高度，用于将 y 坐标裁剪到 `[0, viewportHeight-1]`
  /// - [localToDoc] 将裁剪后的本地坐标映射为文档绝对位置的回调
  ///
  /// 返回：
  /// - 当前指针对应的文档绝对位置（由 [localToDoc] 提供）
  ///
  /// 说明：
  /// - 内部会先将 [local] 裁剪（clamp）到视口范围，避免越界；
  /// - 更新后的裁剪值会保存到 [lastLocal]，供外部逻辑使用。
  int update(
      Offset local,
      double viewportHeight,
      int Function(Offset clampedLocal) localToDoc,
      ) {
    final clamped = Offset(
      local.dx,
      viewportHeight > 1 ? local.dy.clamp(0.0, viewportHeight - 1) : local.dy,
    );
    _lastLocal = clamped;
    return localToDoc(clamped);
  }

  /// 结束拖拽选择，清空内部状态。
  ///
  /// 调用后：
  /// - [isSelecting] 置为 `false`
  /// - [anchor] 和 [lastLocal] 被清空为 `null`
  void end() {
    _isSelecting = false;
    _lastLocal = null;
    _anchor = null;
  }
}
